package com.reebake.ideal.security.util;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTPayload;
import com.reebake.ideal.constants.CommonConstants;
import com.reebake.ideal.security.entity.AccessTokenEntity;
import com.reebake.ideal.security.entity.AuthUser;
import com.reebake.ideal.security.exception.AccessTokenExpiredException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;

/**
 * JWT工具类
 */
public class JwtAuthUtil {
    private static final String SESSION_KEY_PREFIX = "auth:session:";

    public static AccessTokenEntity generate(String username, String userId, Collection<String> authorities, long accessTokenExpireIn) {
        AccessTokenEntity accessTokenEntity = new AccessTokenEntity();
        Date expireAt = new Date(System.currentTimeMillis() + accessTokenExpireIn);
        String accessToken = com.reebake.ideal.crypto.util.JwtUtil.encode(username, userId, authorities, expireAt);
        accessTokenEntity.setAccessToken(accessToken);
        accessTokenEntity.setExpireAt(expireAt);

        return accessTokenEntity;
    }

    public static AuthUser parse(String token, long accessTokenExpiredAhead) {
        JSONObject payloads;
        try {
            JWT jwt = com.reebake.ideal.crypto.util.JwtUtil.decode(token);
            if (!jwt.validate(30)) {
                throw new AccessTokenExpiredException();
            }
            payloads = jwt.getPayloads();
            Date expireAt = payloads.getDate(JWT.EXPIRES_AT);
            long diff = DateUtil.between(DateUtil.date(), expireAt, DateUnit.MS, false);
            if (diff < accessTokenExpiredAhead) {// 提前到期适配token刷新
                throw new AccessTokenExpiredException();
            }
        } catch (Exception e) {
            throw new BadCredentialsException("token invalid", e);
        }
        String subject = payloads.getStr(JWTPayload.SUBJECT);
        JSONArray authorities = payloads.getJSONArray(LambdaUtil.getFieldName(User::getAuthorities));
        Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if (ArrayUtil.isNotEmpty(authorities)) {
            for (Object authority : authorities) {
                grantedAuthorities.add(new SimpleGrantedAuthority(authority.toString()));
            }
        }
        String userId = payloads.getStr(CommonConstants.ATTRIBUTE_NAME_USER_ID);
        AuthUser principal = new AuthUser(subject, token, grantedAuthorities);
        principal.setUserId(userId);

        return principal;
    }

    /**
     * 根据旧token生成新token
     * @param accessTokenExpireIn 过期时间
     * @return 访问token
     */
    public static AccessTokenEntity refresh(long accessTokenExpireIn) {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if(!(principal instanceof AuthUser)) {
            return new AccessTokenEntity();
        }
        AuthUser authUser = (AuthUser) principal;
        String username = authUser.getUsername();
        String userId = authUser.getUserId();
        Collection<String> authorities = new HashSet<>();
        for (GrantedAuthority authority : authUser.getAuthorities()) {
            authorities.add(authority.getAuthority());
        }
        return generate(username, userId, authorities, accessTokenExpireIn);
    }
}
